/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.jeff.springWebTestNG.proxy;

import com.jeff.springWebTestNG.util.MyBeanUtil;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * This example shows how to use JDK Dynamic Proxy package, to implement a 
 * MyBatis/DAL style SQL framework. 
 * 
 * Harnessed with such framework, developer no longer need to provide an 
 * implementation persister object, instead, an interface which is proper 
 * annotated will be fine enough. 
 * 
 * And of course, if we want, it is also easy to make it like 
 * Rails(convention over configuration), if don't like annotation everywhere.
 * 
 * One thing to note is, unlike many other examples, the below implementation 
 * is almost framework level solution(consider its generic way of doing things), 
 * which means you can even directly put it into your J2EE system to work.
 * 
 * @author winnie
 */
public class SqlImplPlayer {
    private static final Logger LOG = Logger.getLogger(SqlImplPlayer.class.getName());
    //cache the proxy persister implementation when initialized, a BeanFactory 
    //will be better, but here we just use a Map to keep simple
    private static Map<Class, Object> persisterCache = new HashMap<Class, Object>();
    
    //From testcase cannot correctly get the classloader, so must run from the class
    public static void main(String[] args) {
        SqlImplPlayer me = new SqlImplPlayer();
        
        me.initPersisters();
        
        ISqlPersister persister = (ISqlPersister) persisterCache.get(ISqlPersister.class);
        persister.updateUser();
        LOG.info("SQL result is : " + persister.getUserTotalCount());
    }    
    
    //try to scan all the persister & initialize the proxy for them
    private void initPersisters() {
        String packageToScan = "com.jeff.springWebTestNG.proxy";
        try {
            List<Class> sqlPersisterClassList = scanForPersister(packageToScan);
            
            for (Class clazz : sqlPersisterClassList) {
                Object persister = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), 
                        new Class[]{clazz}, new InvocationHandler() {

                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object result = null;
                        Annotation annot = method.getAnnotation(SQL.class);
                        if (annot != null) {
                            //grab sql script from annotation instance
                            String sql = ((SQL) annot).value();
                            result = simulateSqlExec(sql);
                        }
                        return result;
                    }
                
                });
                persisterCache.put(clazz, clazz.cast(persister));
            }
        } catch (Exception ex) {
            LOG.log(Level.SEVERE, null, ex);
        }
    }
    
    private Object simulateSqlExec(String sql) {
        Object result = null;
        if (sql.startsWith("update")) {
            result = null;
        } else if (sql.startsWith("select")) {
            result = 10;
        }
        
        LOG.info("SQL executed: " + sql);
        return result;
    }
    
    List<Class> scanForPersister(String packageToScan) {
        List<String> classNameList = MyBeanUtil.scanClassesByPackage(packageToScan);
        List<Class> sqlPersisterClassList = findSqlPersister(classNameList);
        
        return sqlPersisterClassList;
    }
    
    private List<Class> findSqlPersister(List<String> classNameList) {
        List<Class> ret = new ArrayList<Class>();
        
        for (String className : classNameList) {
            try {
                //loadClass() will not throw exception when trying to load an already loaded class,
                //it just bypass it
                Class clazz = Thread.currentThread().getContextClassLoader().loadClass(className);
                Annotation sqlPersisterAnnotation = clazz.getAnnotation(SQLPersister.class);
                if (sqlPersisterAnnotation != null) {
                    ret.add(clazz);
                    LOG.log(Level.FINE, "load class: {0}", clazz.getName());
                    continue;
                }
            } catch (ClassNotFoundException ex) {
                LOG.log(Level.WARNING, null, ex);
            }
        }
        
        return ret;
    }
    
    
}
